/**
 * 
 */
package chatGUI;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.io.File;
import java.util.Observable;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;

import model.SharingFiles;
import model.SharingMessages;
import model.State;
import model.StateChangeException;
import model.SystemState;
import model.User;
import model.UserException;
import model.Users;
import chatController.CharacterNotAcceptedException;
import chatController.ChatController;
import chatController.UsernameEmptyException;

/**
 * @author Pierre LUKJANENKO
 *
 */
/**
 * Platform-specific GUI constructor creating relationships with chat components
 * And initializing disconnected window
 */
public class JavaGUI extends JFrame{

	/**
	 * Attribute for connections between platform-specific GUI and the controller
	 */
	protected ChatController controller; 
	
	/**
	 * Static attribute of user list to read usernames and identify notify source
	 */
	private static Users users;
	
	/**
	 * Static attribute of state of the system to identify notify source
	 */
	private static SystemState state;
	
	/**
	 * Static attribute to identify sent object through notify
	 */
	private static SharingMessages sharingMessage;
	
	/**
	 * Static attribute to identify sent object through notify 
	 */
	private static SharingFiles sharingFile;
	
	/**
	 * Definition of the dimension of the main window (at initialization)
	 */
	private Dimension windowSize = new Dimension(1000,700);
	
	/**
	 * Definition of the text fields size
	 */
	private Dimension txtFieldSize = new Dimension(300,100);
	
	/**
	 * Attribute to keep the note index up to date
	 */
	private int indexNote = 1;
	
	/**
	 * Attribute to keep the current number of active tabs
	 */
	private int indexTab = 0;
	
	//________________________________________________
	//  For Disconnected State (initial state)
	//________________________________________________
	/**
	 * The text fields where the user provides an username
	 */
	private JTextField username;
	/**
	 * The Connection button used to connect to the chat system
	 */
	private JButton bConnection;
	/**
	 * The Panel containing all disconnected window's elements
	 * The system has only one window but alternates between panels.
	 */
	private JPanel paneDisconnected;
	//________________________________________________
	//  For Connected State
	//________________________________________________
	/**
	 * The info Label notifying the user that he is connected and recalls his username
	 */
	private JLabel messageInfo;
	/**
	 * The list of connected users
	 */
	private JList list;
	/**
	 * The model for the list of connected users
	 */
	private DefaultListModel listModel = new DefaultListModel();
	/**
	 * The Disconnect button for the user to quit the chat system
	 */
	private JButton disconnect = new JButton("Disconnect");
	/**
	 * The inside panel containing all conversation and note tabs
	 */
	private JTabbedPane tPane;
	/**
	 * The Text area for the user to provide a text to send
	 */
	private JTextArea txtToSend = new JTextArea();
	/**
	 * The Send button to send the appropriate text as a message
	 */
	private JButton sendButton = new JButton("Send Message");
	/**
	 * The Send File button for the user to select a file to send
	 */
	private JButton sendFileButton = new JButton("Send File");
	/**
	 * The Panel containing the connected users list
	 */
	private JPanel listContainer = new JPanel();
	/**
	 * The Label giving a title to the connected users list
	 */
	private JLabel listLabel = new JLabel("Your fellow users :");
	/**
	 * The menu bar to be displayed only in connected window
	 */
	private MenuBar menuBar;
	/**
	 * The only menu contained in the menu bar, called "Tabs"
	 */
	private Menu tabMenu;
	/**
	 * The menu choice called to close the current (selected) tab
	 */
	private MenuItem tabMenuItemClose;
	/**
	 * The menu choice called to add a note
	 */
	private MenuItem tabMenuItemAdd;
	/**
	 * The global Panel containg all connected window's elements
	 */
	private JPanel paneConnected;
	
	
	/**
	 * @param c
	 * @param u
	 * @param ss
	 * @param sm
	 * @param sf
	 */
	protected JavaGUI(ChatController c, Users u, SystemState ss, SharingMessages sm, SharingFiles sf) {
		super();
		this.controller = c;
		JavaGUI.users = u;
		JavaGUI.state = ss;
		JavaGUI.sharingMessage = sm;
		JavaGUI.sharingFile = sf;
        initWindow();
		initComponents();
	}
	
	
	/**
	 * Getter of the local username
	 * @return the local username
	 */
	protected String getUsername() {
		return username.getText();
	}
	/**
	 * Setter of the local username
	 * Must be called only by controller after correcting provided username
	 * @param u
	 */
	protected void setUsername(String u) {
		this.username.setText(u);
	}
	/**
	 * Getter of the note tab index
	 * @return note tab index
	 */
	protected int getIndexNote() {
		return indexNote;
	}
	/**
	 * Setter of the note tab index
	 * @param i
	 */
	protected void setIndexNote(int i) {
		indexNote = i;
	}
	/**
	 * Getter for the number of tabs (tab max index, notes included)
	 * @return number of tabs
	 */
	protected int getIndexTab() {
		return indexTab;
	}
	/**
	 * Setter of the number of tabs
	 * @param i
	 */
	protected void setIndexTab(int i) {
		indexTab = i;
	}
	/**
	 * Getter of the user list model
	 * @return user list model
	 */
	protected DefaultListModel getListModel() {
		return this.listModel;
	}
	/**
	 * Getter of the user list
	 * @return user list
	 */
	protected JList getList() {
		return this.list;
	}
	/**
	 * Getter of the tab panel
	 * @return tab panel
	 */
	protected JTabbedPane getTPane() {
		return this.tPane;
	}
	
	
	
	/**
	 * Window Initialization
	 */
	private void initWindow() {
		this.setSize(windowSize);
		this.setTitle("Chat System");
		this.addWindowListener(new WindowAdapterForClosing(this));
		this.setVisible(true);
	}
	
	
	/**
	 * Components Initialization
	 */
	private void initComponents() {
		
		//________________________________________________________________
		//  For Disconnected State (initial state)
		//_______________________________________________________________
		username = new JTextField("Enter a username");
		username.setMinimumSize(new Dimension(20,20));
		bConnection = new JButton("Connection");
		bConnection.addActionListener(new ConnectionClickListener(this));
		paneDisconnected = new JPanel();
		paneDisconnected.setSize(windowSize);
		paneDisconnected.add(username);
		paneDisconnected.add(bConnection);
		//paneDisconnected.add(errorNotif);
		this.add(paneDisconnected);
		paneDisconnected.setVisible(false);
		
		//__________________________________________________________________
		//  For Connected State
		//_________________________________________________________________
		
		// MENU BAR
		// Only visible from ConnectedState+BusyState
		menuBar = new MenuBar();
		tabMenu = new Menu("Tabs");
		tabMenuItemClose = new MenuItem("Close Current");
		tabMenuItemClose.addActionListener(new DeleteTabMenuListener(this));
		tabMenu.add(tabMenuItemClose);
		tabMenuItemAdd = new MenuItem("Add note");
		tabMenuItemAdd.addActionListener(new AddNoteMenuListener(this));
		tabMenu.add(tabMenuItemAdd);
		menuBar.add(tabMenu);
				
		messageInfo = new JLabel("OOPS : not connected !");
		paneConnected = new JPanel();
		paneConnected.setLayout(new BorderLayout(35, 0));
		JPanel rightPanel = new JPanel();
		rightPanel.setLayout(new BorderLayout(0, 20));
		JPanel leftPanel = new JPanel();
		leftPanel.setLayout(new BorderLayout(0,15));
		JPanel buttonPanel = new JPanel();
		buttonPanel.setLayout(new GridLayout(0,2, 8, 8));
		
		
		tPane = new JTabbedPane();
		tPane.addChangeListener(new ChangeTabListener(this));
		//tPane.setPreferredSize(convFieldSize);
		addTab("Menu", "Bienvenue dans notre Chat System !\n", false);
				
		rightPanel.add(messageInfo, BorderLayout.NORTH);

		list = new JList(listModel);
		list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		list.setLayoutOrientation(JList.VERTICAL);
		list.addListSelectionListener(new ListListener(this));
		JScrollPane scrollList = new JScrollPane(list);
		//scrollList.setPreferredSize(listFieldSize);
		listContainer.setLayout(new BorderLayout());
		listContainer.add(listLabel, BorderLayout.NORTH);
		listContainer.add(scrollList);
		//startConv.addActionListener(new StartConversationButtonListener(this));
		//listContainer.add(startConv);
		rightPanel.add(listContainer, BorderLayout.CENTER);	
		
		sendButton.addActionListener(new SendButtonListener(this));
		buttonPanel.add(sendButton);			

		disconnect.addActionListener(new DisconnectionClickListener(this));
		buttonPanel.add(disconnect);

		sendFileButton.addActionListener(new SendFileButtonListener(this));
		buttonPanel.add(sendFileButton);
		
		leftPanel.add(tPane, BorderLayout.CENTER);
		
		txtToSend.setPreferredSize(txtFieldSize);
		txtToSend.setLineWrap(true);
		JScrollPane scrollPane = new JScrollPane(txtToSend);
		leftPanel.add(scrollPane, BorderLayout.SOUTH);

		rightPanel.add(buttonPanel, BorderLayout.SOUTH);
		paneConnected.add(leftPanel, BorderLayout.CENTER);
		paneConnected.add(rightPanel, BorderLayout.EAST);
		
		this.setVisible(true);
		paneConnected.setVisible(false);
		this.add(paneConnected);
	}
	
	//________________________________________________________________
	// 	Functional Methods
	//________________________________________________________________
	
	/**
	 * Adds a tab with a title and optionally a content to the tab Panel
	 * @param titre
	 * @param contenu
	 * @param editable
	 */
	protected void addTab(String titre, String contenu, boolean editable) {
		JTextArea pane = new JTextArea();
		pane.setLineWrap(true);
		pane.setEditable(editable);
		pane.append(contenu);
		JScrollPane scrollPane = new JScrollPane(pane);
		tPane.add(scrollPane, titre, indexTab);
		tPane.getComponent(indexTab);
		indexTab ++;
	}
	
	/**
	 * Deletes the current (selected) tab without confirmation
	 */
	protected void deleteTab() {
		int index;
		if ((index = tPane.getSelectedIndex()) > 0){
			tPane.remove(index);
			indexTab --;
		}
	}
	
	/**
	 * Forwards the connection action to the controller with sending the provided username to be corrected
	 */
	protected void connect() {
		try {
			this.controller.performConnect(username.getText());
		} catch (UsernameEmptyException e) {
			JOptionPane.showMessageDialog(this, "Your username cannot be empty !");
		} catch (CharacterNotAcceptedException e) {
			JOptionPane.showMessageDialog(this, "Your username cannot contain :  \" nor \\ nor '");
		}
	}
	
	/**
	 * Demands to the chat controller to perform a disconnection
	 */
	protected void disconnect() {
		try {
			this.controller.performDisconnect();
			while(indexTab>1) {
				tPane.remove(indexTab-1);
				indexTab --;
			}
			this.username.setText("Enter a username");
		} catch (StateChangeException e) {
			JOptionPane.showMessageDialog(this, e.toString());
		}
	}
	
	/**
	 * Displays a pop-up message indicating the connection has been aborted
	 */
	protected void connectionAborted() {
		JOptionPane.showMessageDialog(this, "Connection Aborted !");
	}
	
	/**
	 * Forwards to the chat controller the message to be sent and demands to send it
	 */
	protected void sendMessage() {
		User dest = selectedContact();
		if (dest != null) {
			String message = txtToSend.getText();
			if (!message.equals("")) {
				txtToSend.setText("");
				this.controller.performSend(message, dest);
			}
		}
		else if (tPane.getTitleAt(tPane.getSelectedIndex()).contains("Note ")) {
			displayNote(txtToSend.getText());
			txtToSend.setText("");
		}
		else {
			JOptionPane.showMessageDialog(this, "Please select valid user first.");;
		}
	}
	
	/**
	 * Forwards the action of sending a file to the controller with the file to be sent
	 */
	protected void sendFile() {
		User dest = selectedContact();
		if (dest != null) {
			JFileChooser fileChooser = new JFileChooser();
			fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);

			int result = fileChooser.showOpenDialog(this);
			if(result == JFileChooser.APPROVE_OPTION){
				File myFile = new File(fileChooser.getSelectedFile().getAbsolutePath());
				try {
					this.controller.sendFile(myFile, dest);
				} catch (StateChangeException e) {
					JOptionPane.showMessageDialog(this, e.toString());
				}
			}
		}
		else {
			JOptionPane.showMessageDialog(this, "Please select valid user first.");
		}
	}
	
	// Because conversation with one person only
	/**
	 * Checks if there is a selected tab, and if so, returns the related user
	 * @return selected user if found
	 */
	protected User selectedContact() {
		User found = new User("","");
		try {
			int index;
			if ((index = getList().getSelectedIndex()) != -1){
				found = users.getUser(getListModel().getElementAt(index).toString());
				return found;
			}
			else {
				return null;
			}
		} catch (UserException e) {
			System.out.println(e);
			return null;
		}
	}
	
	/**
	 * Selects the tab linked the selected user
	 */
	protected void selectTab() {
		User u;
		if ((u = selectedContact()) != null){
			openConversationWindow(u.toString());
		}
	}
	
	/**
	 * Selects the list contact corresponding to the selected tab
	 */
	protected void selectContact() {
		if (indexTab > 0) {
			int index = tPane.getSelectedIndex();
			if (listModel.contains(tPane.getTitleAt(index)) == true){
				list.setSelectedValue(tPane.getTitleAt(index), true);
			}
		}
	}
	
	/**
	 * Displays the info message according to the controller-corrected local username
	 */
	protected void infoConnected() {
		messageInfo.setText("You're now connected as " + users.getLocalUsername() + ".");
		this.setVisible(true);
	}
	
	/**
	 * Forwards the window closing action to the chat controller
	 */
	protected void closeWindow() {
		try {
			this.controller.performCloseWindow();
		} catch (StateChangeException e) {
			JOptionPane.showMessageDialog(this, e.toString());
		}
	}
	
	/**
	 * Updates the list display
	 */
	protected void displayList() {
		listContainer.setVisible(true);
	}
	
	/**
	 * Display a message to the user to confirm a file reception
	 * @param user
	 * @param name
	 */
	protected void displayDemand(User user, String name) {    
		int result = JOptionPane.showConfirmDialog(this, user + " wants to send you " 
						+ name + ". Do you accept?", 
						"Accept file", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);

		if(result == JOptionPane.OK_OPTION){
			JFileChooser dirChooser = new JFileChooser();
			dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

			int ret = dirChooser.showOpenDialog(this);
			if(ret == JFileChooser.APPROVE_OPTION){
				String dest = dirChooser.getSelectedFile().getAbsolutePath();
				try {
					this.controller.performReceiveFile(dest);
				} catch (StateChangeException e) {
					JOptionPane.showMessageDialog(this, e.toString());
				}
			}      
		}
		else {
			this.controller.refuseReceiveFile();
		}
	}
	
	/**
	 * Open the tab (if already created) corresponding to the selected user
	 * If not created yet, creates the tab and get it selected
	 * @param selectedUser
	 */
	protected void openConversationWindow(String selectedUser) {
		try {
			int index;
			if ((index = tPane.indexOfTab(users.getUser(selectedUser).toString())) == -1) {
				this.addTab(users.getUser(selectedUser).toString(),"", false);
			}
			else {
				tPane.setSelectedIndex(index); 
			}
		} catch (UserException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Displays a sent message according to the destination user
	 * @param u
	 * @param m
	 */
	protected void displayMessage(User u, String m) {
		JScrollPane scrollArea;
		JTextArea textArea;
		m = users.getLocalUser().getUsername() + " : " + m;
		
		int index = tPane.indexOfTab(u.toString());
		if (index != -1) {
			scrollArea = (JScrollPane)tPane.getComponentAt(index);
			textArea = (JTextArea) scrollArea.getViewport().getView();
			textArea.append(m);
		}
		else {
			this.addTab(u.toString(), m, false);
		}
	}
	
	/**
	 * Allows the user to write a note to himself like he would send a text message
	 * @param m
	 */
	private void displayNote(String m) {
		JScrollPane scrollArea;
		JTextArea textArea;
		m = users.getLocalUser().getUsername() + " : " + m + "\n";
		
		int index = tPane.getSelectedIndex();
		if (index != -1) {
			scrollArea = (JScrollPane)tPane.getComponentAt(index);
			textArea = (JTextArea) scrollArea.getViewport().getView();
			textArea.append(m);
		}
		else {
			this.addTab("Note "+indexNote, m, false);
			indexNote ++;
		}
	}
	
	/**
	 * Displays a received message according to the source user
	 * @param u
	 * @param m
	 */
	protected void displayRcvd(User u, String m) {
		JScrollPane scrollArea;
		m = u.getUsername() + " : " + m;
		if (tPane.indexOfTab(u.toString()) == -1) {
			addTab(u.toString(),"", false);
		}
		scrollArea = (JScrollPane)tPane.getComponent(tPane.indexOfTab(u.toString()));
		((JTextArea) scrollArea.getViewport().getView()).append(m);
	}
        
	/**
	 * Notifies the user in console his window has been closed
	 */
	protected void windowClosed() {
		System.out.println("Window closed. Exiting...");
	}
	
	/**
	 * Updates the platform-specific GUI's windows and components according to the signal sent by the observable model
	 * @param arg0
	 * @param arg1
	 */
	protected void update(Observable arg0, Object arg1) {
		// Si la notification vient de la liste des users connectes
		if (arg0 == users) {
			// l'objet envoy� est le nouveau user connecte, on l'ajoute � la liste
			listContainer.setVisible(false);
			if (arg1 instanceof User) {
				if ((User)arg1 != users.getLocalUser()) {
					try {
						users.getUser(arg1.toString());
						listModel.addElement(((User)arg1).toString());
					} catch (UserException e) {
						listModel.removeElement(((User)arg1).toString());
						users.checkOut();
					}
				}
			}
			displayList();
		}
		// Si la notification vient du System State => nous faire changer la vue
		if (arg0 == state) {
			// Si on s'est connect�s, on cache la vue Disconnected et on montre la vue Connected
			if ((State)arg1 == State.connected) {
				infoConnected();
				// On active le menu pour les onglets
				this.setMenuBar(menuBar);
				// On r�active les boutons
				//this.startConv.setEnabled(true);
				this.disconnect.setEnabled(true);
				// On afRcvfiche la vue adequate (comme Shiela)
				this.paneDisconnected.setVisible(false);
				this.paneConnected.setVisible(true);
			}
			// Si on est deconnect�s, on cache la vue Connected et on montre la vue Disconnected
			else if ((State)arg1 == State.disconnected) {
				// On desactive le menu des onglets (puisqu'on en a plus/pas encore)
				this.setMenuBar(null);
				this.paneConnected.setVisible(false);
				this.paneDisconnected.setVisible(true);
			}
			// Si on est occupes (ie on envoie un fichier), on empeche la deconnexion
			// On desactive le bouton Disconnect
			else if ((State)arg1 == State.busy) {
				this.disconnect.setEnabled(false);
			}
			// On se d�connecte, on ne veut plus pouvoir se d�connecter ou commencer de conversation
			else if ((State)arg1 == State.disconnecting) {
				this.disconnect.setEnabled(false);
				//this.startConv.setEnabled(false);
			}
			else if ((State)arg1 == State.closing) {
				this.dispose();
				windowClosed();
			}
			// Pas plus de cas
		}
		if (arg0 == sharingMessage) {
			if (sharingMessage.isToSend()) {
				displayMessage(sharingMessage.getUser(), sharingMessage.getText() + "\n");
			} else {
				displayRcvd(sharingMessage.getUser(), sharingMessage.getText() + "\n");
			}
		}
		if (arg0 == sharingFile) {
			if (!sharingFile.getWaitRemoteAnswer()) {
				// Quelqu'un veut nous envoyer un fichier 
				if (!sharingFile.isToSend() && !sharingFile.isSent()) {
					displayDemand(sharingFile.getUser(), sharingFile.getFileName());
				}
				// Remote app a répondu a une demande de notre part
				else if (sharingFile.isToSend() && !sharingFile.isSent()) {
					if (sharingFile.isAccepted()) {
						JOptionPane.showMessageDialog(this, sharingFile.getUser().toString() + 
													" accepted your file. Sending...");
					}
					else {
						JOptionPane.showMessageDialog(this, sharingFile.getUser().toString() + 
													" refused your file.");
					}
				}
				else if (sharingFile.isToSend() && sharingFile.isSent()) {
					JOptionPane.showMessageDialog(this, sharingFile.getFileName() + " sent!");
				}
				else if (!sharingFile.isToSend() && sharingFile.isSent()) {
					JOptionPane.showMessageDialog(this, sharingFile.getFileName() + " received!");
				}
			}
		}
	}
	
}
